using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HybridCLR;

/// <summary>
/// 
/// </summary>
public class XApp : MonoBehaviour
{
    /// <summary>
    /// HybridCLR super set mode
    /// </summary>
    public HomologousImageMode homologousImageMode = HomologousImageMode.SuperSet;

    /// <summary>
    /// Hotfix assembly main entry type
    /// </summary>
    [Header("Hotfix plugin main type")]
    public string hotfixPluginMainType;

    /// <summary>
    /// Hotfix assembly main file
    /// </summary>
    [Header("Hotfix assembly file name")]
    public string hotfixAssemblyFile;

    /// <summary>
    /// supplementary metadata from AOT assemblys
    /// </summary>
    [Header("Configure AOT assemblys")]
    public string[] aotAssemblys;

    /// <summary>
    /// active hotfix
    /// </summary>
    [Header("Enable hotfix")]
    public bool hotfix;

    /// <summary>
    /// 
    /// </summary>
    [Header("Hotfix break window")]
    public XHotfixWindow hotfixWindow;

    /// <summary>
    /// 
    /// </summary>
    [Header("Enable hotfix debug mode, delete local resource sheet file")]
    public bool hotfixDebugMode;

    /// <summary>
    /// 
    /// </summary>
    [Header("Hotfix version file Url")]
    public string hotfixVersionFileURL;

    /// <summary>
    /// 
    /// </summary>
    [Header("Enable Console to screen")]
    public bool consoleToScreen;

    /// <summary>
    /// 
    /// </summary>
    [Header("Resource load source")]
    public XResourceLoadSource source = XResourceLoadSource.Resources;

    /// <summary>
    /// hotfix CDN type
    /// </summary>
    public enum XHotfixCDNType
    {
        None,
        Dev,
    }

    /// <summary>
    /// 
    /// </summary>
    [Header("Hotfix CDN root dir")]
    public XHotfixCDNType cdnType = XHotfixCDNType.Dev;

    /// <summary>
    /// app install
    /// </summary>
    /// 
    private void Awake()
    {
        Application.targetFrameRate = 60;

        if (consoleToScreen)
            gameObject.AddComponent<XConsoleToScreen>();

#if UNITY_EDITOR || UNITY_DEVELOP
        GLog.Enable = true;
#else
        GLog.Enable = false;
#endif
        DontDestroyOnLoad(gameObject);
    }

    // Start is called before the first frame update
    /// <summary>
    /// start game, check hotfix update
    /// </summary>
    /// <returns></returns>
    IEnumerator Start()
    {
        bool isBreaking = false;
        if (hotfix)
        {
            if (string.IsNullOrEmpty(hotfixVersionFileURL))
                throw new System.ArgumentNullException($"Enable hotfix, but hotfix version file url is empty");

            if (hotfixWindow)
            {
                XHotfix.Initialize(hotfixWindow, (breakWindow) => {
                    GLog.Log("[Hotfix] Initialize succeed");
                });
            }
 
            // check hotfix update, download hotfix file and reset client version
            yield return StartCoroutine(XHotfix.DoUpdate(hotfix, GetUpdateRootUrl(), delegate (XHotfixStatus status, float progress, string message) {
                switch (status)
                {
                    case XHotfixStatus.LocalVersion:
                        GLog.Log("[Hotfix] status {0} {1}", status.ToString(), message);
                        break;

                    case XHotfixStatus.BootVersion:
                        GLog.Log("[Hotfix] status {0} {1}", status.ToString(), message);
                        break;

                    case XHotfixStatus.DownloadApk:
                        isBreaking = true;
                        GLog.Log("[Hotfix] status {0} {1}", status.ToString(), message);
                        break;

                    case XHotfixStatus.DownloadRes:
                        GLog.Log("[Hotfix] status {0} {1}", status.ToString(), message);
                        break;

                    case XHotfixStatus.Error:
                        GLog.LogError("[Hotfix] status {0} {1}", status.ToString(), message);
                        break;

                    default:
                        break;
                }
            }, hotfixDebugMode));
        }
        else
        {
            yield return XCoroutine.Run(XRes.Initialize(() => {
                GLog.Log("[XApp] XRes.Initialize finished");
            }, source, hotfixDebugMode));
        }

        if (!isBreaking)
        {
            if (string.IsNullOrEmpty(hotfixPluginMainType) || string.IsNullOrEmpty(hotfixAssemblyFile))
                throw new System.NullReferenceException($"you must configure hotfix main and hotfix dll file");

            // in release env, we must be load AOT assemblys from files
#if !UNITY_EDITOR
            LoadAsyncMetadataForAOTAssemblys(aotAssemblys, (Assembly[] aotAssemblys) => {
                LoadAsyncHotfixAssembly((Assembly[] assemblys) => {
                    Run(assemblys);
                });
            });
#else
            Assembly[] assemblys = System.AppDomain.CurrentDomain.GetAssemblies();
            if (assemblys.Length != 0)
            {
                Run(assemblys);
            }
#endif
        }
    }

    /// <summary>
    /// Load supplementary metadata from AOT assemblys
    /// </summary>
    /// <param name="aotAssemblys"></param>
    /// <param name="complete"></param>
    /// <returns></returns>
    bool LoadAsyncMetadataForAOTAssemblys(string[] aotAssemblys, System.Action<Assembly[]> complete)
    {
        if (aotAssemblys.Length <= 0)
        {
            complete?.Invoke(new Assembly[] { });
            return false;
        }

        XRes.LoadMultiAsync(aotAssemblys, (UnityEngine.Object[] files) => {
            List<Assembly> assemblys = new List<Assembly>();
            for (int i = 0; i < files.Length; i++)
            {
                TextAsset asset = files[i] as TextAsset;
                if (asset.bytes.Length != 0)
                {
                    LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(asset.bytes, homologousImageMode);
                    if (err != LoadImageErrorCode.OK)
                        throw new System.ArgumentNullException($"load metadta for AOT assembly error {err}");

#if !UNITY_EDITOR && !UNITY_REALEASE
                    GLog.Log("Load aot assembly {0} length {1} code {2}", aotAssemblys[i], asset.bytes.Length, err.ToString());
#endif
                }
            }

            complete?.Invoke(assemblys.ToArray());
        });

        return true;
    }

    /// <summary>
    /// load hotfix assembly in file
    /// </summary>
    /// <param name="hotfixAssemblys"></param>
    /// <param name="complete"></param>
    void LoadAsyncHotfixAssembly(System.Action<Assembly[]> complete)
    {
        string hotfixFilePath = System.IO.Path.Combine(XDirectory.Assemblys.ToString(), hotfixAssemblyFile).Replace("\\", "/").ToLower();
        XRes.LoadMultiAsync(new string[] { hotfixFilePath }, (UnityEngine.Object[] files) => {
            List<Assembly> assemblys = new List<Assembly>();
            for (int i=0; i<files.Length; i++)
            {
                TextAsset asset = files[i] as TextAsset;
                if (asset.bytes.Length != 0)
                {
                    Assembly assembly = XAssemblyHelper.Load(asset.bytes);
                    if (assembly == null)
                        throw new System.DllNotFoundException($"can't load hotfix assembly {files[i]} length {asset.bytes.Length}");

#if !UNITY_EDITOR
                    GLog.Log("Load hofix assembly {0} length {1}", files[i], asset.bytes.Length);
#endif
                    assemblys.Add(assembly);
                }
            }
            
            complete?.Invoke(assemblys.ToArray());
        });
    }

    /// <summary>
    /// run hotxfix assembly
    /// </summary>
    void Run(Assembly[] assemblys)
    {
        List<System.Type> types = new List<System.Type>();
        foreach (Assembly assembly in assemblys)
        {
            if (assembly.GetName().Name == hotfixAssemblyFile)
            {
                var type = assembly.GetType(hotfixPluginMainType);
                if (type != null)
                    types.Add(type);
            }
        }

        if (types.Count <= 0)
            throw new System.DllNotFoundException($"Can't find dll type {hotfixPluginMainType} in file {hotfixAssemblyFile}");

#if !UNITY_EDITOR
        foreach(System.Type type in types)
        {
            GLog.Log("[App] run load type {0}", type.ToString());
        }
#endif

        XRootContext.GetSingleton().Startup(types.ToArray());
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public string GetUpdateRootUrl()
    {
        XHotfixTarget target = XHotfix.GetHotfixTarget();

        // get current app AOT version info
        string curMainVersion = string.Format("{0}.{1}", XHotfixVersion.appVersion, XHotfixVersion.mainVersion);

        // set hotfix version file
        string updateRootURL = string.Empty;
        if (!string.IsNullOrEmpty(hotfixVersionFileURL))
            updateRootURL = System.IO.Path.Combine(hotfixVersionFileURL, cdnType.ToString(), curMainVersion, target.ToString()).Replace("\\", "/");

        return updateRootURL;
    }

    /// <summary>
    /// 
    /// </summary>
    private void OnApplicationQuit()
    {
        XRootContext.GetSingleton().Shutdown();
    }
}
